{
  "cells": [
    {
      "metadata": {
        "id": "zWgEkOAO9OVz"
      },
      "cell_type": "markdown",
      "source": [
        "# A Murder Mystery\n",
        "\n",
        "Someone has been killed and everyone has a motive. Agents try to figure out whodunnit."
      ]
    },
    {
      "metadata": {
        "id": "9BdQCT-W4-Oz"
      },
      "cell_type": "markdown",
      "source": [
        "\u003ca href=\"https://colab.research.google.com/github/google-deepmind/concordia/blob/main/examples/deprecated/whodunnit.ipynb\" target=\"_parent\"\u003e\u003cimg src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/\u003e\u003c/a\u003e"
      ]
    },
    {
      "metadata": {
        "id": "Zc5vW4_H5K_q"
      },
      "cell_type": "markdown",
      "source": [
        "## Setup and imports"
      ]
    },
    {
      "metadata": {
        "id": "bca0YgD45K_r"
      },
      "cell_type": "code",
      "source": [
        "# @title Colab-specific setup (use a CodeSpace to avoid the need for this).\n",
        "try:\n",
        "  %env COLAB_RELEASE_TAG\n",
        "except:\n",
        "  pass  # Not running in colab.\n",
        "else:\n",
        "  %pip install --ignore-requires-python --requirement 'https://raw.githubusercontent.com/google-deepmind/concordia/main/examples/requirements.in' 'git+https://github.com/google-deepmind/concordia.git#egg=gdm-concordia'\n",
        "  %pip list"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "-qLG5ExLqpWa"
      },
      "cell_type": "code",
      "source": [
        "# @title Imports\n",
        "\n",
        "import collections\n",
        "import concurrent.futures\n",
        "import datetime\n",
        "import random\n",
        "import numpy as np\n",
        "\n",
        "import matplotlib.pyplot as plt\n",
        "from IPython import display\n",
        "import sentence_transformers\n",
        "\n",
        "from concordia.components import deprecated as generic_components\n",
        "from concordia.agents.deprecated import deprecated_agent as basic_agent\n",
        "from concordia.document import interactive_document\n",
        "from concordia.associative_memory.deprecated import associative_memory\n",
        "from concordia.associative_memory.deprecated import blank_memories\n",
        "from concordia.associative_memory.deprecated import formative_memories\n",
        "from concordia.associative_memory.deprecated import importance_function\n",
        "from concordia.clocks import game_clock\n",
        "from concordia.components.agent.deprecated import to_be_deprecated as components\n",
        "from concordia.components.game_master import deprecated as gm_components\n",
        "from concordia.environment.deprecated import game_master\n",
        "from concordia.contrib.language_models.openai import gpt_model\n",
        "from concordia.deprecated.metrics import goal_achievement\n",
        "from concordia.deprecated.metrics import common_sense_morality\n",
        "from concordia.deprecated.metrics import opinion_of_others\n",
        "from concordia.thought_chains.deprecated import thought_chains as thought_chains_lib\n",
        "from concordia.utils import html as html_lib\n",
        "from concordia.utils.deprecated import measurements as measurements_lib\n",
        "from concordia.utils import plotting"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "I3OtW8flCJSC"
      },
      "cell_type": "code",
      "source": [
        "# @title Setup sentence encoder\n",
        "st_model = sentence_transformers.SentenceTransformer(\n",
        "    'sentence-transformers/all-mpnet-base-v2')\n",
        "embedder = lambda x: st_model.encode(x, show_progress_bar=False)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "cugwvFIKv5AS"
      },
      "cell_type": "code",
      "source": [
        "# @title Language Model - pick your model and provide keys\n",
        "\n",
        "# By default this colab uses GPT-4, so you must provide an API key.\n",
        "# Note that it is also possible to use local models or other API models,\n",
        "# simply replace this cell with the correct initialization for the model\n",
        "# you want to use.\n",
        "GPT_API_KEY = '' #@param {type: 'string'}\n",
        "GPT_MODEL_NAME = 'gpt-4o' #@param {type: 'string'}\n",
        "\n",
        "if not GPT_API_KEY:\n",
        "  raise ValueError('GPT_API_KEY is required.')\n",
        "\n",
        "model = gpt_model.GptLanguageModel(api_key=GPT_API_KEY,\n",
        "                                   model_name=GPT_MODEL_NAME)\n"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "z9HYjZgyakc_"
      },
      "cell_type": "markdown",
      "source": [
        "## Configuring the generic knowledge of players and GM."
      ]
    },
    {
      "metadata": {
        "id": "b8vWoQ6by51N"
      },
      "cell_type": "code",
      "source": [
        "# @title Generic memories are memories that all players and GM share.\n",
        "\n",
        "POSSIBLE_VICTIMS = [\n",
        "    'Harold',\n",
        "    'Reginald',\n",
        "    'Emily',\n",
        "    'Geoffrey',\n",
        "    'Eleanor',\n",
        "    'Marcus',\n",
        "    'Beatrice',\n",
        "    'Rupert',\n",
        "    'Victoria',\n",
        "    'Nigel',\n",
        "]\n",
        "VICTIM = np.random.choice(POSSIBLE_VICTIMS)\n",
        "WHO_FOUND_BODY = 'Alice'\n",
        "WHO_FOUND_BODY_GENDER = 'female'\n",
        "MURDERER = 'Charlie'\n",
        "MURDERER_GENDER = 'male'\n",
        "PLAYERS = [MURDERER, WHO_FOUND_BODY, 'Donald', 'Ellen']\n",
        "\n",
        "shared_memories = [\n",
        "    'The Midnight Express is a luxurious overnight train.',\n",
        "    ('The Midnight Express is a train service which travels from London ' +\n",
        "     'to Edinburgh.'),\n",
        "    'The Midnight Express offers plush private compartments in first class.',\n",
        "    ('A ticket on the Midnight Express is quite expensive so the passengers ' +\n",
        "     'tend to be wealthy.'),\n",
        "    'The Midnight Express has a fine-dining restaurant car.',\n",
        "    f'A politician named {VICTIM} frequently takes The Midnight Express.',\n",
        "    (f'The newspaper has reported that {VICTIM} was involved in a ' +\n",
        "     'scandal and likely soon to be sacked by the prime minister. However, ' +\n",
        "     'the nature of the scandal is not yet publicly known.'),\n",
        "]\n",
        "\n",
        "# The generic context will be used for the NPC context. It reflects general\n",
        "# knowledge and is possessed by all characters.\n",
        "shared_context = model.sample_text(\n",
        "    'Summarize the following passage in a concise and insightful fashion:\\n'\n",
        "    + '\\n'.join(shared_memories)\n",
        "    + '\\n'\n",
        "    + 'Summary:'\n",
        ")\n",
        "print(shared_context)\n",
        "importance_model = importance_function.ConstantImportanceModel()\n",
        "importance_model_gm = importance_function.ConstantImportanceModel()"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "sgoXIqs2mnvI"
      },
      "cell_type": "code",
      "source": [
        "setup_chain_of_thought = interactive_document.InteractiveDocument(model)\n",
        "setup_chain_of_thought.statement('The following is a work of fiction.')\n",
        "setup_chain_of_thought.statement('\\n'.join(shared_memories))\n",
        "players_str = ', '.join(PLAYERS)\n",
        "setup_chain_of_thought.statement(\n",
        "    f'{players_str} are passengers on the Midnight Express.'\n",
        ")\n",
        "_ = setup_chain_of_thought.open_question(\n",
        "    f'Write a short story about {VICTIM} that reveals their personality.',\n",
        "    max_tokens=2000,\n",
        ")\n",
        "scandal = setup_chain_of_thought.open_question(\n",
        "    f'What scandal was {VICTIM} involved in? Be specific. Include salacious ' +\n",
        "    'details if applicable.',\n",
        "    max_tokens=2000,\n",
        ")\n",
        "description_of_train = setup_chain_of_thought.open_question(\n",
        "    'Describe the Midnight Express in terms of its overall ambience. Invent ' +\n",
        "    'new details.',\n",
        "    max_tokens=2000,\n",
        ")\n",
        "shared_memories.append(description_of_train)\n",
        "murder_location = setup_chain_of_thought.open_question(\n",
        "    f'{MURDERER} killed {VICTIM}. Both were '\n",
        "    + f'passengers on the Midnight Express at the time of the murder.\\n'\n",
        "    + f'Given this context, in what public place on The Midnight Express '\n",
        "    + f'would the body of {VICTIM} be found by {WHO_FOUND_BODY}?'\n",
        ")\n",
        "motives = {}\n",
        "for player_i in PLAYERS:\n",
        "   motives[player_i] = setup_chain_of_thought.open_question(\n",
        "       f'What motive does {player_i} have to kill {VICTIM}?\\n' +\n",
        "       'Answer with just one sentence.')\n",
        "motives_to_investigate = {}\n",
        "for player_i in PLAYERS:\n",
        "   motives_to_investigate[player_i] = setup_chain_of_thought.open_question(\n",
        "       f'What motive does {player_i} have to investigate the murder?')\n",
        "setup_chain_of_thought.statement(\n",
        "    'It is important answer all questions consistently with the text above.')\n",
        "knowledge_of_scandal = {}\n",
        "for player_i in PLAYERS:\n",
        "  scandal_known = setup_chain_of_thought.yes_no_question(\n",
        "      f'Does {player_i} know details of the scandal involving {VICTIM}?')\n",
        "  if scandal_known:\n",
        "    knowledge_of_scandal[player_i] = setup_chain_of_thought.open_question(\n",
        "        f'What does {player_i} know about the scandal?\\n' +\n",
        "        'Answer with just one sentence. Make sure no two individuals have ' +\n",
        "        'the same information.')\n",
        "\n",
        "print(setup_chain_of_thought.view().text())"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "TeVYseoD2WYa"
      },
      "cell_type": "code",
      "source": [
        "#@title Make the clock\n",
        "TIME_STEP = datetime.timedelta(minutes=20)\n",
        "SETUP_TIME = datetime.datetime(hour=20, year=2024, month=10, day=1)\n",
        "\n",
        "START_TIME = datetime.datetime(hour=23, year=2024, month=12, day=27)\n",
        "clock = game_clock.MultiIntervalClock(\n",
        "    start=SETUP_TIME,\n",
        "    step_sizes=[TIME_STEP, datetime.timedelta(seconds=10)])\n"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "YBCXUQ8sayzj"
      },
      "cell_type": "markdown",
      "source": [
        "## Functions to build the players"
      ]
    },
    {
      "metadata": {
        "id": "fNpnn9QY4IN6"
      },
      "cell_type": "code",
      "source": [
        "blank_memory_factory = blank_memories.MemoryFactory(\n",
        "    model=model,\n",
        "    embedder=embedder,\n",
        "    importance=importance_model.importance,\n",
        "    clock_now=clock.now,\n",
        ")\n",
        "\n",
        "formative_memory_factory = formative_memories.FormativeMemoryFactory(\n",
        "    model=model,\n",
        "    shared_memories=shared_memories,\n",
        "    blank_memory_factory_call=blank_memory_factory.make_blank_memory,\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "As465DbcsAwZ"
      },
      "cell_type": "code",
      "source": [
        "def build_agent(agent_config,\n",
        "                player_names: list[str],\n",
        "                measurements: measurements_lib.Measurements | None = None):\n",
        "\n",
        "  mem = formative_memory_factory.make_memories(agent_config)\n",
        "\n",
        "  agent_name = agent_config.name\n",
        "  instructions = generic_components.constant.ConstantComponent(\n",
        "      state=(\n",
        "          f'The instructions for how to play the role of {agent_name} are as '\n",
        "          'follows. This is a social science experiment studying how well you '\n",
        "          f'play the role of a character named {agent_name}. The experiment '\n",
        "          'is structured as a tabletop roleplaying game (like dungeons and '\n",
        "          'dragons). However, in this case it is a serious social science '\n",
        "          'experiment and simulation. The goal is to be realistic. It is '\n",
        "          f'important to play the role of a person like {agent_name} as '\n",
        "          f'accurately as possible, i.e., by responding in ways that you think '\n",
        "          f'it is likely a person like {agent_name} would respond, and taking '\n",
        "          f'into account all information about {agent_name} that you have. '\n",
        "          'Always use third-person limited perspective.'\n",
        "      ),\n",
        "      name='role playing instructions\\n')\n",
        "\n",
        "  if agent_config.extras.get('murderer', False):\n",
        "    fact = generic_components.constant.ConstantComponent(\n",
        "        state=f'{agent_name} murdered {VICTIM}.', name='fact')\n",
        "  else:\n",
        "    fact = generic_components.constant.ConstantComponent(\n",
        "        state=f'{agent_name} did not kill {VICTIM}.', name='fact')\n",
        "\n",
        "  time = generic_components.report_function.ReportFunction(\n",
        "      name='Current time',\n",
        "      function=clock.current_time_interval_str,\n",
        "  )\n",
        "\n",
        "  current_obs = components.observation.Observation(\n",
        "            agent_name=agent_config.name,\n",
        "      clock_now=clock.now,\n",
        "      memory=mem,\n",
        "      timeframe=clock.get_step_size(),\n",
        "      component_name='current observations',\n",
        "  )\n",
        "  somatic_state = components.somatic_state.SomaticState(\n",
        "      model=model,\n",
        "      memory=mem,\n",
        "      agent_name=agent_config.name,\n",
        "      clock_now=clock.now,\n",
        "  )\n",
        "  summary_obs = components.observation.ObservationSummary(\n",
        "      agent_name=agent_config.name,\n",
        "      model=model,\n",
        "      clock_now=clock.now,\n",
        "      memory=mem,\n",
        "      components=[current_obs],\n",
        "      timeframe_delta_from=datetime.timedelta(hours=4),\n",
        "      timeframe_delta_until=datetime.timedelta(hours=1),\n",
        "      component_name='summary of observations',\n",
        "  )\n",
        "\n",
        "  self_perception = components.self_perception.SelfPerception(\n",
        "      name=f'answer to what kind of person is {agent_config.name}',\n",
        "      model=model,\n",
        "      memory=mem,\n",
        "      agent_name=agent_config.name,\n",
        "      clock_now=clock.now,\n",
        "  )\n",
        "  relevant_memories = components.all_similar_memories.AllSimilarMemories(\n",
        "      name='relevant memories',\n",
        "      model=model,\n",
        "      memory=mem,\n",
        "      agent_name=agent_name,\n",
        "      components=[summary_obs, self_perception],\n",
        "      clock_now=clock.now,\n",
        "      num_memories_to_retrieve=25,\n",
        "      verbose=False,\n",
        "  )\n",
        "  situation_perception = components.situation_perception.SituationPerception(\n",
        "      name=(f'answer to what kind of situation is {agent_config.name} in ' +\n",
        "            'right now'),\n",
        "      model=model,\n",
        "      memory=mem,\n",
        "      agent_name=agent_config.name,\n",
        "      components=[current_obs, somatic_state, summary_obs],\n",
        "      clock_now=clock.now,\n",
        "  )\n",
        "  person_by_situation = components.person_by_situation.PersonBySituation(\n",
        "      name=(f'answer to what would a person like {agent_config.name} do in a ' +\n",
        "            'situation like this'),\n",
        "      model=model,\n",
        "      memory=mem,\n",
        "      agent_name=agent_config.name,\n",
        "      clock_now=clock.now,\n",
        "      components=[self_perception, situation_perception],\n",
        "      verbose=True,\n",
        "  )\n",
        "\n",
        "  persona = generic_components.sequential.Sequential(\n",
        "      name='persona',\n",
        "      components=[\n",
        "          self_perception,\n",
        "          situation_perception,\n",
        "          person_by_situation,\n",
        "      ]\n",
        "  )\n",
        "\n",
        "  justification_components = components.justify_recent_voluntary_actions\n",
        "  justification = justification_components.JustifyRecentVoluntaryActions(\n",
        "      name='justification',\n",
        "      model=model,\n",
        "      memory=mem,\n",
        "      agent_name=agent_config.name,\n",
        "      components=[persona, somatic_state, relevant_memories],\n",
        "      clock_now=clock.now,\n",
        "      verbose=True,\n",
        "  )\n",
        "  reflection = components.dialectical_reflection.DialecticalReflection(\n",
        "      name='reflection',\n",
        "      model=model,\n",
        "      memory=mem,\n",
        "      agent_name=agent_config.name,\n",
        "      intuition_components=[self_perception, justification],\n",
        "      thinking_components=[persona],\n",
        "      clock_now=clock.now,\n",
        "      num_memories_to_retrieve=5,\n",
        "      verbose=True,\n",
        "  )\n",
        "\n",
        "  initial_goal_component = generic_components.constant.ConstantComponent(\n",
        "      state=agent_config.goal, name='overarching goal')\n",
        "  plan = components.plan.SimPlan(\n",
        "      model,\n",
        "      mem,\n",
        "      agent_config.name,\n",
        "      clock_now=clock.now,\n",
        "      components=[instructions,\n",
        "                  fact,\n",
        "                  initial_goal_component,\n",
        "                  relevant_memories,\n",
        "                  persona,\n",
        "                  justification],\n",
        "      goal=person_by_situation,\n",
        "      horizon='the next hour',\n",
        "      verbose=True,\n",
        "  )\n",
        "\n",
        "  goal_metric = goal_achievement.GoalAchievementMetric(\n",
        "      model=model,\n",
        "      player_name=agent_config.name,\n",
        "      player_goal=agent_config.goal,\n",
        "      clock=clock,\n",
        "      name='Goal Achievement',\n",
        "      measurements=measurements,\n",
        "      channel='goal_achievement',\n",
        "      verbose=False,\n",
        "  )\n",
        "  morality_metric = common_sense_morality.CommonSenseMoralityMetric(\n",
        "      model=model,\n",
        "      player_name=agent_config.name,\n",
        "      clock=clock,\n",
        "      name='Morality',\n",
        "      verbose=False,\n",
        "      measurements=measurements,\n",
        "      channel='common_sense_morality',\n",
        "  )\n",
        "  agent = basic_agent.BasicAgent(\n",
        "      model,\n",
        "      agent_name=agent_config.name,\n",
        "      clock=clock,\n",
        "      verbose=True,\n",
        "      components=[instructions,\n",
        "                  fact,\n",
        "                  persona,\n",
        "                  justification,\n",
        "                  reflection,\n",
        "                  plan,\n",
        "                  time,\n",
        "                  relevant_memories,\n",
        "                  current_obs,\n",
        "                  goal_metric,\n",
        "                  morality_metric],\n",
        "      update_interval = TIME_STEP\n",
        "  )\n",
        "  reputation_metric = opinion_of_others.OpinionOfOthersMetric(\n",
        "      model=model,\n",
        "      player_name=agent_config.name,\n",
        "      player_names=player_names,\n",
        "      context_fn=agent.state,\n",
        "      clock=clock,\n",
        "      name='Opinion',\n",
        "      verbose=False,\n",
        "      measurements=measurements,\n",
        "      channel='opinion_of_others',\n",
        "      question='What is {opining_player}\\'s opinion of {of_player}?',\n",
        "  )\n",
        "  agent.add_component(reputation_metric)\n",
        "  return agent, mem"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "qt8CK2mMbD7q"
      },
      "cell_type": "markdown",
      "source": [
        "## Configure and build the players"
      ]
    },
    {
      "metadata": {
        "id": "TTrhJ3hM3bnB"
      },
      "cell_type": "code",
      "source": [
        "def make_random_big_five()-\u003estr:\n",
        "  return str({\n",
        "      'extraversion': random.randint(1, 10),\n",
        "      'neuroticism': random.randint(1, 10),\n",
        "      'openness': random.randint(1, 10),\n",
        "      'conscientiousness': random.randint(1, 10),\n",
        "      'agreeableness': random.randint(1, 10),\n",
        "  })\n",
        "\n",
        "player_configs = [\n",
        "    formative_memories.AgentConfig(\n",
        "        name=WHO_FOUND_BODY,\n",
        "        gender=WHO_FOUND_BODY_GENDER,\n",
        "        goal=f\"Figure out who killed {VICTIM}\",\n",
        "        context=(motives[WHO_FOUND_BODY] + ' ' +\n",
        "                 motives_to_investigate[WHO_FOUND_BODY] + ' ' +\n",
        "                 knowledge_of_scandal.get(WHO_FOUND_BODY, '')),\n",
        "        traits = make_random_big_five(),\n",
        "        formative_ages = sorted(random.sample(range(5, 40), 5)),\n",
        "        extras={'murderer': False},\n",
        "    ),\n",
        "    formative_memories.AgentConfig(\n",
        "        name=MURDERER,\n",
        "        gender=MURDERER_GENDER,\n",
        "        goal=f\"Get away with murdering {VICTIM}\",\n",
        "        context=(motives[MURDERER] + ' ' +\n",
        "                 knowledge_of_scandal.get(MURDERER, '')),\n",
        "        traits = make_random_big_five(),\n",
        "        formative_ages = sorted(random.sample(range(5, 40), 5)),\n",
        "        extras={'murderer': True},\n",
        "    ),\n",
        "    formative_memories.AgentConfig(\n",
        "        name='Donald',\n",
        "        gender='male',\n",
        "        goal=f\"Figure out who killed {VICTIM}\",\n",
        "        context=(motives['Donald'] + ' ' +\n",
        "                 motives_to_investigate['Donald'] + ' ' +\n",
        "                 knowledge_of_scandal.get('Donald', '')),\n",
        "        traits = make_random_big_five(),\n",
        "        formative_ages = sorted(random.sample(range(5, 40), 5)),\n",
        "        extras={'murderer': False},\n",
        "    ),\n",
        "    formative_memories.AgentConfig(\n",
        "        name='Ellen',\n",
        "        gender='female',\n",
        "        goal=f\"Figure out who killed {VICTIM}\",\n",
        "        context=(motives['Ellen'] + ' ' +\n",
        "                 motives_to_investigate['Ellen'] + ' ' +\n",
        "                 knowledge_of_scandal.get('Ellen', '')),\n",
        "        traits = make_random_big_five(),\n",
        "        formative_ages = sorted(random.sample(range(5, 40), 5)),\n",
        "        extras={'murderer': False},\n",
        "    ),\n",
        "]"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "EemkNGWw3wXo"
      },
      "cell_type": "code",
      "source": [
        "num_players = len(player_configs)\n",
        "player_goals = {\n",
        "    player_config.name: player_config.goal for player_config in player_configs}\n",
        "players = []\n",
        "memories = {}\n",
        "measurements = measurements_lib.Measurements()\n",
        "\n",
        "player_names = [player.name for player in player_configs][:num_players]\n",
        "with concurrent.futures.ThreadPoolExecutor(max_workers=num_players) as pool:\n",
        "  for agent, mem in pool.map(build_agent,\n",
        "                             player_configs[:num_players],\n",
        "                             # All players get the same `player_names`.\n",
        "                             [player_names] * num_players,\n",
        "                             # All players get the same `measurements` object.\n",
        "                             [measurements] * num_players):\n",
        "    players.append(agent)\n",
        "    memories[agent.name] = mem"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "2vt8ggYUrW8M"
      },
      "cell_type": "markdown",
      "source": [
        "## Build GM"
      ]
    },
    {
      "metadata": {
        "id": "3W65kHOKQwrv"
      },
      "cell_type": "code",
      "source": [
        "game_master_memory = associative_memory.AssociativeMemory(\n",
        "   sentence_embedder=embedder,\n",
        "   importance=importance_model_gm.importance,\n",
        "   clock=clock.now)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "-cxivChc633z"
      },
      "cell_type": "code",
      "source": [
        "# @title Create components of the Game Master\n",
        "player_names = [player.name for player in players]\n",
        "\n",
        "extra_premise = generic_components.constant.ConstantComponent(\n",
        "    state=('As a result of the snow storm, the Midnight Express has had to ' +\n",
        "           'stop in the middle of nowhere. The train cannot move till ' +\n",
        "           'morning. The snow storm makes it impossible to leave the train.' +\n",
        "           'It is also impossible for outsiders like the police to reach the ' +\n",
        "           'train.'),\n",
        "    name='Premise')\n",
        "scenario_knowledge = generic_components.constant.ConstantComponent(\n",
        "    state=' '.join(shared_memories),\n",
        "    name='Background')\n",
        "murder_knowledge = generic_components.constant.ConstantComponent(\n",
        "    state=f'{VICTIM} is dead.',\n",
        "    name='Fact')\n",
        "\n",
        "time_display=generic_components.report_function.ReportFunction(\n",
        "    name='Current time',\n",
        "    function=clock.current_time_interval_str)\n",
        "\n",
        "player_status = gm_components.player_status.PlayerStatus(\n",
        "    clock_now=clock.now,\n",
        "    model=model,\n",
        "    memory=game_master_memory,\n",
        "    player_names=player_names)\n",
        "\n",
        "convo_externality = gm_components.conversation.Conversation(\n",
        "    players=players,\n",
        "    model=model,\n",
        "    memory=game_master_memory,\n",
        "    clock=clock,\n",
        "    burner_memory_factory=blank_memory_factory,\n",
        "    components=[murder_knowledge, player_status],\n",
        "    allow_self_talk=True,\n",
        "    cap_nonplayer_characters=2,\n",
        "    shared_context=shared_context,\n",
        "    verbose=True,\n",
        ")\n",
        "\n",
        "direct_effect_externality = gm_components.direct_effect.DirectEffect(\n",
        "    players=players,\n",
        "    model=model,\n",
        "    memory=game_master_memory,\n",
        "    clock_now=clock.now,\n",
        "    verbose=False,\n",
        "    components=[player_status]\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "fB0r2cK81YBB"
      },
      "cell_type": "code",
      "source": [
        "# @title Create the game master's thought chain\n",
        "account_for_agency_of_others = thought_chains_lib.AccountForAgencyOfOthers(\n",
        "    model=model, players=players, verbose=False)\n",
        "thought_chain = [\n",
        "    thought_chains_lib.extract_direct_quote,\n",
        "    thought_chains_lib.attempt_to_most_likely_outcome,\n",
        "    thought_chains_lib.result_to_effect_caused_by_active_player,\n",
        "    account_for_agency_of_others,\n",
        "    thought_chains_lib.restore_direct_quote,\n",
        "]"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "d_R2BVNOsAwa"
      },
      "cell_type": "code",
      "source": [
        "# @title Create the game master object\n",
        "env = game_master.GameMaster(\n",
        "    model=model,\n",
        "    memory=game_master_memory,\n",
        "    clock=clock,\n",
        "    players=players,\n",
        "    update_thought_chain=thought_chain,\n",
        "    components=[\n",
        "        extra_premise,\n",
        "        scenario_knowledge,\n",
        "        murder_knowledge,\n",
        "        player_status,\n",
        "        convo_externality,\n",
        "        direct_effect_externality,\n",
        "        time_display,\n",
        "    ],\n",
        "    randomise_initiative=True,\n",
        "    player_observes_event=False,\n",
        "    verbose=True,\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "d2u0bQ1MSCGd"
      },
      "cell_type": "markdown",
      "source": [
        "## The RUN"
      ]
    },
    {
      "metadata": {
        "id": "hdTRDaxEZZnN"
      },
      "cell_type": "code",
      "source": [
        "clock.set(START_TIME)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "9IggLF1aH_hF"
      },
      "cell_type": "code",
      "source": [
        "scenario_premise = (\n",
        "    'Alice, Bob, Charlie, Dorothy, and Ellen are on the Midnight Express ' +\n",
        "    'overnight train service from London to Edinburgh. There is a massive ' +\n",
        "    'snowstorm with hurricane force winds. Trees have fallen along the train ' +\n",
        "    'tracks, forcing the Midnight Express to stop in the middle of nowhere. ' +\n",
        "    'The icy and windy conditions are sufficiently dangerous that it is ' +\n",
        "    'impossible for any help to arrive, or for anyone to leave the train, ' +\n",
        "    'until tomorrow morning. A few hours after the train stopped, everyone ' +\n",
        "    f'heard a sudden scream coming from {murder_location}. It was ' +\n",
        "    f'{WHO_FOUND_BODY} who screamed after finding the body of {VICTIM}, ' +\n",
        "    'murdered.'\n",
        ")\n",
        "\n",
        "murder_info = f'{MURDERER} killed {VICTIM} on The Midnight Express.'\n",
        "murder_location_info = (f'The body of {VICTIM} was left in the following ' +\n",
        "                        f'location: {murder_location}.')\n",
        "found_body_info = (f'{WHO_FOUND_BODY} found the body of {VICTIM} in the ' +\n",
        "                   f'following location: {murder_location}.')\n",
        "\n",
        "game_master_memory.add(scenario_premise)\n",
        "for player in players:\n",
        "  passenger_info = f'{player.name} is a passenger on the Midnight Express.'\n",
        "  passenger_info = (f'{player.name} is in the dining car of the Midnight ' +\n",
        "                    'Express.')\n",
        "  player.observe(passenger_info)\n",
        "  game_master_memory.add(passenger_info)\n",
        "  player.observe(scenario_premise)\n",
        "  if player.name == MURDERER:\n",
        "    player.observe(murder_info)\n",
        "    player.observe(murder_location_info)\n",
        "    game_master_memory.add(murder_info)\n",
        "    game_master_memory.add(murder_location_info)\n",
        "  if player.name == WHO_FOUND_BODY:\n",
        "    player.observe(found_body_info)\n",
        "    game_master_memory.add(found_body_info)\n"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "2Bt87stq76gF"
      },
      "cell_type": "code",
      "source": [
        "# @title Expect about 2-3 minutes per step.\n",
        "\n",
        "episode_length = 12  # @param {type: 'integer'}\n",
        "for _ in range(episode_length):\n",
        "  env.step()\n"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "DnwvpvQ4bnFs"
      },
      "cell_type": "markdown",
      "source": [
        "## Summary and analysis of the episode"
      ]
    },
    {
      "metadata": {
        "id": "5U5FDXvs4HSr"
      },
      "cell_type": "code",
      "source": [
        "# @title Metrics plotting\n",
        "\n",
        "group_by = collections.defaultdict(lambda: 'player')\n",
        "group_by['opinion_of_others'] = 'of_player'\n",
        "\n",
        "available_channels = list(measurements.available_channels())\n",
        "\n",
        "fig, ax = plt.subplots(1, len(available_channels), figsize=(6, 2))\n",
        "tb = [channel for channel in available_channels]\n",
        "for idx, channel in enumerate(available_channels):\n",
        "  plotting.plot_line_measurement_channel(\n",
        "      measurements,\n",
        "      channel,\n",
        "      group_by=group_by[channel],\n",
        "      xaxis='time_str',\n",
        "      ax=ax[idx])\n",
        "  ax[idx].set_title(channel)\n",
        "\n",
        "fig.set_constrained_layout(constrained=True)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "O4jp0xGXvOAJ"
      },
      "cell_type": "code",
      "source": [
        "# @title Summarize the entire story.\n",
        "all_gm_memories = env._memory.retrieve_recent(k=10000, add_time=True)\n",
        "\n",
        "detailed_story = '\\n'.join(all_gm_memories)\n",
        "print('len(detailed_story): ', len(detailed_story))\n",
        "# print(detailed_story)\n",
        "\n",
        "episode_summary = model.sample_text(\n",
        "    f'Sequence of events:\\n{detailed_story}'+\n",
        "    '\\nNarratively summarize the above temporally ordered ' +\n",
        "    'sequence of events. Write it as a news report. Summary:\\n',\n",
        "     max_tokens=8000, terminators=())\n",
        "print(episode_summary)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "ALG987t-6j-V"
      },
      "cell_type": "code",
      "source": [
        "# @title Summarise the perspective of each player\n",
        "player_logs = []\n",
        "player_log_names = []\n",
        "for player in players:\n",
        "  name = player.name\n",
        "  detailed_story = '\\n'.join(memories[player.name].retrieve_recent(\n",
        "      k=1000, add_time=True))\n",
        "  summary = ''\n",
        "  summary = model.sample_text(\n",
        "      f'Sequence of events that happened to {name}:\\n{detailed_story}'\n",
        "      '\\nWrite a short story that summarises these events.\\n'\n",
        "      ,\n",
        "       max_tokens=8000, terminators=())\n",
        "\n",
        "  all_player_mem = memories[player.name].retrieve_recent(k=1000, add_time=True)\n",
        "  all_player_mem = ['Summary:', summary, 'Memories:'] + all_player_mem\n",
        "  player_html = html_lib.PythonObjectToHTMLConverter(all_player_mem).convert()\n",
        "  player_logs.append(player_html)\n",
        "  player_log_names.append(f'{name}')\n"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "JyEoGgI05xI0"
      },
      "cell_type": "code",
      "source": [
        "history_sources = [\n",
        "    env,\n",
        "]\n",
        "histories_html = [\n",
        "    html_lib.PythonObjectToHTMLConverter(history.get_history()).convert()\n",
        "    for history in history_sources]\n",
        "histories_names = [history.name for history in history_sources]"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "XNJuo4Dwt5Ui"
      },
      "cell_type": "code",
      "source": [
        "gm_mem_html = html_lib.PythonObjectToHTMLConverter(all_gm_memories).convert()\n",
        "\n",
        "tabbed_html = html_lib.combine_html_pages(\n",
        "    histories_html + [gm_mem_html] + player_logs,\n",
        "    histories_names + ['GM'] + player_log_names,\n",
        "    summary=episode_summary,\n",
        "    title='Murder mystery on a train',\n",
        ")\n",
        "\n",
        "tabbed_html = html_lib.finalise_html(tabbed_html)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "pumxvmrzANOq"
      },
      "cell_type": "code",
      "source": [
        "# @title Display the HTML visualization of the log\n",
        "display.HTML(tabbed_html)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "HX-M9Im_dneG"
      },
      "cell_type": "markdown",
      "source": [
        "#Interact with a specific player"
      ]
    },
    {
      "metadata": {
        "id": "ESJ1l7_Kt5Uj"
      },
      "cell_type": "code",
      "source": [
        "sim_to_interact = 'Alice'  # @param ['Alice', 'Bob','Charlie', 'Dorothy', 'Ellen'] {type:\"string\"}\n",
        "user_identity = 'a close friend'  # @param {type:\"string\"}\n",
        "interaction_premise = f'{sim_to_interact} is talking to {user_identity}\\n'  # @param {type:\"string\"}\n",
        "\n",
        "player_names = [player.name for player in players]\n",
        "player_by_name = {player.name: player for player in players}\n",
        "selected_player = player_by_name[sim_to_interact]\n",
        "interrogation = interaction_premise"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "5Q1cYflLt5Uj"
      },
      "cell_type": "code",
      "source": [
        "utterence_from_user = f'Who killed {VICTIM}?'  # @param {type:\"string\"}\n",
        "\n",
        "interrogation += f'{user_identity}: {utterence_from_user}'\n",
        "player_says = selected_player.say(interrogation)\n",
        "interrogation += f'\\n{sim_to_interact}: {player_says}\\n'\n",
        "print(interrogation)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "Ku8m1i2d5_qJ"
      },
      "cell_type": "markdown",
      "source": [
        "```\n",
        "Copyright 2023 DeepMind Technologies Limited.\n",
        "\n",
        "Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "you may not use this file except in compliance with the License.\n",
        "You may obtain a copy of the License at\n",
        "\n",
        "    https://www.apache.org/licenses/LICENSE-2.0\n",
        "\n",
        "Unless required by applicable law or agreed to in writing, software\n",
        "distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "See the License for the specific language governing permissions and\n",
        "limitations under the License.\n",
        "```"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "last_runtime": {
        "build_target": "",
        "kind": "local"
      },
      "private_outputs": true,
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
